package cn.isjinhao.se.sql.grammar.statement;

import cn.isjinhao.se.sql.function.evaluator.FunctionEvaluator;
import cn.isjinhao.se.sql.function.handler.date.format.DateConst;
import cn.isjinhao.se.sql.grammar.CurdStatement;
import cn.isjinhao.se.sql.grammar.GrammarErrorReporterDelegate;
import cn.isjinhao.se.sql.grammar.statement.AssignmentList.Entry;
import cn.isjinhao.se.sql.grammar.statement.retrieve.represention.GroupFunction;
import cn.isjinhao.se.sql.grammar.statement.retrieve.represention.SingleSelect;
import cn.isjinhao.se.sql.lexical.scan.TokenSequence;
import cn.isjinhao.se.sql.lexical.token.Token;
import cn.isjinhao.se.sql.lexical.token.TokenType;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * whereSeg         ->  "where" logic
 * <p>
 * logic            ->  condition (("or" | "and") condition)*
 * condition        ->  comparison
 * comparison       ->  binaryArithmetic ((">" | "<" | ">=" | "<=" | "!=" | "=" | "like" | "contains") binaryArithmetic)?
 * binaryArithmetic ->  unaryArithmetic (("+" | "-" | "*" | "/") unaryArithmetic)*
 * unaryArithmetic  ->  ("!"|"-") unaryArithmetic | primary
 * <p>
 * primary          ->  #{xxx} | ? | "true" | "false" | "null" | NUMBER | STRING | IDENTIFIER | function
 * function         ->  functionName "(" binaryArithmetic? ("," binaryArithmetic)* ")"
 * functionName     ->  "date_to_timestamp" | "date_to_str" | "str_to_date" | "str_to_timestamp" | "timestamp_to_date" | "timestamp_to_str" | "now"
 * <p>
 * assignmentList   ->  (IDENTIFIER "=" binaryArithmetic) ("," IDENTIFIER "=" binaryArithmetic)*
 * <p>
 * <p>
 * AbstractParser需要满足一个特点：tokenSequence永远指向下一次即将处理的token的位置。
 *
 * @Author ISJINHAO
 * @Date 2021/4/5 12:11
 */
public class StatementParser extends AbstractCurdStatementParser {

    protected StatementParser(TokenSequence tokenSequence, FunctionEvaluator functionEvaluator) {
        super(tokenSequence, functionEvaluator);
    }

    @Override
    public CurdStatement parse() {
        return logic();
    }

    /**
     * "where" logic
     */
    protected CurdStatement whereSeg() {
        consume(TokenType.WHERE, GrammarErrorReporterDelegate.STATEMENT_whereSeg_PARSE);
        return new WhereSeg(logic());
    }

    /**
     * condition (("or" | "and") condition)*
     */
    protected CurdStatement logic() {
        CurdStatement left = condition();
        while (tokenSequence.curEqual(TokenType.OR, TokenType.AND)) {
            Token token = tokenSequence.takeCur();
            tokenSequence.advance();
            CurdStatement right = condition();
            left = new Logic(left, token, right);
        }
        return left;
    }


    /**
     * comparison
     */
    protected CurdStatement condition() {
        return comparison();
    }


    /**
     * binaryArithmetic ((">" | "<" | ">=" | "<=" | "!=" | "=" | "like" | "contains" | "is") binaryArithmetic)?
     */
    protected CurdStatement comparison() {
        CurdStatement left = binaryArithmetic();
        Token token = comparisonSymbol();
        if (token != null) {
            CurdStatement right = binaryArithmetic();
            assertIs(token, right);
            return new Comparison(left, token, right);
        }
        return left;
    }

    private static final Token IS = new Token(TokenType.IS, "is");
    private static final Token NULL = new Token(TokenType.NULL, "null");


    private void assertIs(Token token, CurdStatement right) {
        if (!IS.equals(token)) {
            return;
        }

        if (!(right instanceof Literal)) {
            error(GrammarErrorReporterDelegate.STATEMENT_is_PARSE);
            return;
        }

        Token value = ((Literal) right).getValue();

        if (!NULL.equals(value)) {
            error(GrammarErrorReporterDelegate.STATEMENT_is_PARSE);
        }
    }


    /**
     * ">" | "<" | ">=" | "<=" | "!=" | "=" | "like" | "contains" | "is"
     */
    private Token comparisonSymbol() {
        if (tokenSequence.curEqual(TokenType.LIKE, TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.LESS,
                TokenType.LESS_EQUAL, TokenType.BANG_EQUAL, TokenType.EQUAL, TokenType.CONTAINS, TokenType.IS)) {
            Token token = tokenSequence.takeCur();
            tokenSequence.advance();
            return token;
        }
        return null;
    }


    /**
     * unaryArithmetic (("+" | "-" | "*" | "/") unaryArithmetic)*
     */
    protected CurdStatement binaryArithmetic() {
        CurdStatement expression = unaryArithmetic();
        while (tokenSequence.curEqual(TokenType.PLUS, TokenType.MINUS,
                TokenType.STAR, TokenType.SLASH)) {
            Token operator = tokenSequence.takeCur();
            tokenSequence.advance();
            CurdStatement right = unaryArithmetic();
            expression = new BinaryArithmetic(expression, operator, right);
        }
        return expression;
    }


    /**
     * ("!"|"-") unaryArithmetic | primary
     */
    protected CurdStatement unaryArithmetic() {
        // "!-123"是不符合语法规则的。但我们将这个问题推迟到运行时检测。
        if (tokenSequence.curEqual(TokenType.BANG, TokenType.MINUS)) {
            Token operator = tokenSequence.takeCur();
            tokenSequence.advance();
            CurdStatement curdStatement = unaryArithmetic();
            return new UnaryArithmetic(operator, curdStatement);
        }
        return primary();
    }


    /**
     * #{xxx} | ? | "true" | "false" | "null" | NUMBER | STRING | IDENTIFIER | function
     */
    protected CurdStatement primary() {
        if (tokenSequence.equalThenAdvance(TokenType.FALSE, TokenType.TRUE, TokenType.NULL, TokenType.NUMBER, TokenType.STRING, TokenType.PARAMETER, TokenType.HASH_MARK_PLACEHOLDER)) {
            return new Literal(tokenSequence.takePre());
        }

        // IDENTIFIER
        if (tokenSequence.curEqual(TokenType.IDENTIFIER)) {
            Token current = tokenSequence.takeCur();
            Token next = tokenSequence.takeNext();
            if (checkFunction(current, next)) {
                return function();
            }
            tokenSequence.advance();
            return new Identifier(current);
        }

        return null;
    }


    /**
     * functionName "(" binaryArithmetic? ("," binaryArithmetic)* ")"
     */
    protected CurdStatement function() {
        Token method = tokenSequence.takeCur();
        tokenSequence.advance();
        consume(TokenType.LEFT_PAREN, GrammarErrorReporterDelegate.STATEMENT_FUNCTION_PARSE);

        // 没有参数的情况
        if (tokenSequence.equalThenAdvance(TokenType.RIGHT_PAREN)) {
            return new Function(method);
        }

        // 有参数的情况
        ArrayList<CurdStatement> parameterList = new ArrayList<>();
        do {
            CurdStatement curdStatement;
            Token current = tokenSequence.takeCur();
            Token next = tokenSequence.takeNext();
            if (checkFunction(current, next)) {
                curdStatement = function();
            } else if (TokenType.INTERVAL.equals(current.getType())) {
                curdStatement = timeInterval();
            } else if (checkTimeUnit(current, next)) {
                curdStatement = timeUnit();
            } else {
                curdStatement = logic();
            }
            assertFunctionParameter(curdStatement);
            parameterList.add(curdStatement);
        } while (tokenSequence.equalThenAdvance(TokenType.COMMA));
        consume(TokenType.RIGHT_PAREN, GrammarErrorReporterDelegate.STATEMENT_FUNCTION_PARSE);
        Function function = new Function(method, parameterList);
        doFunctionStaticCheck(function);
        return function;
    }

    private CurdStatement timeUnit() {
        Token unit = tokenSequence.takeCur();
        tokenSequence.advance();
        consume(TokenType.FROM, GrammarErrorReporterDelegate.STATEMENT_timeUnit_PARSE);
        CurdStatement curdStatement = primary();

        return new TimeUnit(unit, curdStatement);
    }

    protected CurdStatement timeInterval() {
        consume(TokenType.INTERVAL, GrammarErrorReporterDelegate.STATEMENT_timeInterval_PARSE);

        Token time;
        Token token;

        if (tokenSequence.curEqual(TokenType.NUMBER)) {
            time = tokenSequence.takeCur();
            tokenSequence.advance();
        } else {
            error(GrammarErrorReporterDelegate.STATEMENT_timeInterval_PARSE);
            return null;
        }

        if (tokenSequence.curEqual(TokenType.IDENTIFIER)) {
            token = tokenSequence.takeCur();
            tokenSequence.advance();
        } else {
            error(GrammarErrorReporterDelegate.STATEMENT_timeInterval_PARSE);
            return null;
        }
        Long interval = null;
        Object value = time.getLiteral();
        if ((value instanceof Integer || value instanceof Long)) {
            interval = Long.parseLong(value.toString());
        }

        assertIntervalType(token);
        return new TimeInterval(token, interval);
    }

    private void assertIntervalType(Token token) {
        if (token == null || !TokenType.IDENTIFIER.equals(token.getType())) {
            error(GrammarErrorReporterDelegate.STATEMENT_timeInterval_CHECK);
        }
        String type = (String) token.getLiteral();
        if (type == null || !timeTypeSet.contains(type)) {
            error(GrammarErrorReporterDelegate.STATEMENT_timeInterval_CHECK);
        }
    }

    private static final Set<String> timeTypeSet = new HashSet<>();

    static {
        timeTypeSet.add(DateConst.MICROSECOND_STR);
        timeTypeSet.add(DateConst.SECOND_STR);
        timeTypeSet.add(DateConst.MINUTE_STR);
        timeTypeSet.add(DateConst.HOUR_STR);
        timeTypeSet.add(DateConst.DAY_STR);
        timeTypeSet.add(DateConst.WEEK_STR);
        timeTypeSet.add(DateConst.MONTH_STR);
        timeTypeSet.add(DateConst.QUARTER_STR);
        timeTypeSet.add(DateConst.YEAR_STR);
    }

    /**
     * (IDENTIFIER "=" binaryArithmetic) ("," IDENTIFIER "=" binaryArithmetic)*
     */
    protected CurdStatement assignmentList() {
        List<Entry> entryList = new ArrayList<>();
        do {
            Token token = consume(TokenType.IDENTIFIER, GrammarErrorReporterDelegate.STATEMENT_assignmentList_PARSE);
            consume(TokenType.EQUAL, GrammarErrorReporterDelegate.STATEMENT_assignmentList_PARSE);
            CurdStatement curdStatement = binaryArithmetic();
            entryList.add(new Entry(token, curdStatement));
        } while (tokenSequence.equalThenAdvance(TokenType.COMMA));
        return new AssignmentList(entryList);
    }


    private void assertFunctionParameter(CurdStatement curdStatement) {
        if (curdStatement instanceof BinaryArithmetic) {
            BinaryArithmetic binaryArithmetic = (BinaryArithmetic) curdStatement;
            assertFunctionParameter(binaryArithmetic.getLeftSelectStatement());
            assertFunctionParameter(binaryArithmetic.getRightSelectStatement());
        } else if (curdStatement instanceof UnaryArithmetic) {
            UnaryArithmetic unaryArithmetic = (UnaryArithmetic) curdStatement;
            assertFunctionParameter(unaryArithmetic.getSelectStatement());
        } else if (curdStatement instanceof GroupFunction
                || curdStatement instanceof SingleSelect || curdStatement instanceof Grouping) {
            error(GrammarErrorReporterDelegate.STATEMENT_STATIC_FUNCTION_CHECK);
        }
    }

    protected Token consume(TokenType tokenType, int errorCode) {
        Token consume = tokenSequence.consume(tokenType);
        if (consume == null) {
            error(errorCode);
            return null;
        }
        return consume;
    }

}
